#pragma once

#include "adalog/LogEvent.h"
#include "adalog/LogLevel.h"
#include "adalog/LogAppender.h"
#include "adalog/LogFormatter.h"
#include <list>
#include <memory>
#include <unordered_map>

#if __cplusplus >= 202002L
    #include <source_location>
#endif

#if __cplusplus >= 202002L
    #define ADALOG_DEFAULT_DEBUG(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Debug(payload);
    #define ADALOG_DEFAULT_INFO (payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Info (payload);
    #define ADALOG_DEFAULT_WARN (payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Warn (payload);
    #define ADALOG_DEFAULT_ERROR(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Error(payload);
    #define ADALOG_DEFAULT_FATAL(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Fatal(payload);

    #define ADALOG_DEBUG(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Debug(payload);
    #define ADALOG_INFO (logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Info (payload);
    #define ADALOG_WARN (logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Warn (payload);
    #define ADALOG_ERROR(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Error(payload);
    #define ADALOG_FATAL(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Fatal(payload);
#else
    #define ADALOG_DEFAULT_DEBUG(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Debug(payload, __FILE__, __LINE__);
    #define ADALOG_DEFAULT_INFO(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Info(payload, __FILE__, __LINE__);
    #define ADALOG_DEFAULT_WARN(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Warn(payload, __FILE__, __LINE__);
    #define ADALOG_DEFAULT_ERROR(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Error(payload, __FILE__, __LINE__);
    #define ADALOG_DEFAULT_FATAL(payload) adalog::LoggerManager::GetInstance().GetLogger("default_logger")->Fatal(payload, __FILE__, __LINE__);

    #define ADALOG_DEBUG(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Debug(payload, __FILE__, __LINE__);
    #define ADALOG_INFO(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Info(payload, __FILE__, __LINE__);
    #define ADALOG_WARN(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Warn(payload, __FILE__, __LINE__);
    #define ADALOG_ERROR(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Error(payload, __FILE__, __LINE__);
    #define ADALOG_FATAL(logger_name, payload) adalog::LoggerManager::GetInstance().GetLogger(logger_name)->Fatal(payload, __FILE__, __LINE__);
#endif

namespace adalog 
{
    class Logger
    {
    public:
        using Ptr = std::shared_ptr<Logger>;
        Logger(const std::string& logger_name, 
               LogLevel::Value log_level, 
               std::list<LogAppender::Ptr> appenders,
               LogFormatter::Ptr formatter = std::make_shared<LogFormatter>());

        std::string GetLoggerName() const;
        LogLevel::Value GetLogLevel() const;

        void AddAppender(LogAppender::Ptr appender);

        void Log(LogLevel::Value log_level, LogEvent::Ptr event);

#if __cplusplus >= 202002L
        void Debug(const std::string& payload, std::source_location src_loc = std::source_location::current());
        void Info(const std::string& payload, std::source_location src_loc = std::source_location::current());
        void Warn(const std::string& payload, std::source_location src_loc = std::source_location::current());
        void Error(const std::string& payload, std::source_location src_loc = std::source_location::current());
        void Fatal(const std::string& payload, std::source_location src_loc = std::source_location::current());
#else
        void Debug(const std::string& payload, 
                   const std::string& file_name,
                   size_t line);
        void Info (const std::string& payload, 
                   const std::string& file_name,
                   size_t line);
        void Warn (const std::string& payload, 
                   const std::string& file_name,
                   size_t line);
        void Error(const std::string& payload, 
                   const std::string& file_name,
                   size_t line);
        void Fatal(const std::string& payload, 
                   const std::string& file_name,
                   size_t line);
#endif

    private:
        std::string logger_name_;
        LogLevel::Value log_level_;
        std::list<LogAppender::Ptr> appenders_;
        LogFormatter::Ptr formatter_;
    };

    class LoggerBuilder
    {
    public:
        LoggerBuilder();
        LoggerBuilder& BuildLoggerName(const std::string& logger_name);
        LoggerBuilder& BuildLogLevel(LogLevel::Value log_level);
        LoggerBuilder& BuildAppender(LogAppender::Ptr appender);

        Logger::Ptr BuildLogger();

    private:
        std::string logger_name_;
        LogLevel::Value log_level_;
        std::list<LogAppender::Ptr> appenders_;
    };

    class LoggerManager
    {
    public:
        static LoggerManager& GetInstance();

        void AddLogger(Logger::Ptr logger);
        Logger::Ptr GetLogger(const std::string& logger_name);

    private:
        LoggerManager();
        std::unordered_map<std::string, Logger::Ptr> logger_table_;
    };
} // namespace adalog